package com.qianfeng.websocket;

import com.qianfeng.Event.DeviceCommandEvent;
import com.qianfeng.Event.DeviceStatusEvent;
import com.qianfeng.task.CheckStatusTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/*
 *@Description:
 *@author:刘哥
 *@Date:86180 2022/9/14 19:42
 */
@Component
public abstract class BaseWebSocket extends TextWebSocketHandler {
    private ApplicationContext context;
    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }
//    保存会话使用
    private Map<String, WebSocketSession> allClients=new HashMap<>();

    //key是设备的id,value是最后连接的时间
    private ConcurrentHashMap<String, Long> updateTimes = new ConcurrentHashMap();//保存设备最后的会话时间


    /**
     * 连接成功之后
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //我们的目的最终是为了控制设备,所以我们必须知道设备的会话是什么,所以在这里我们必须把会话保存
        //我们还是约定在地址的最后一位拼接我们的设备的编号,获取路径
        String path = session.getUri().getPath();
//        截取设备id
        String deviceId = path.substring(path.lastIndexOf("/") + 1);
//       存起来方便用
        session.getAttributes().put("deviceId",deviceId);
//        将设备id作为键保存到会话中用来判断会话是否存在,关闭或开启会话是方便用
        WebSocketSession webSocketSession = allClients.get(deviceId);
//        如果之前创建的有会话,关闭掉
        if (webSocketSession != null&&webSocketSession.isOpen()) {
            webSocketSession.close();
        }
//        保存会话
        allClients.put(deviceId,session);
//        连接成功,修改设备上线状态和时间
        context.publishEvent(new DeviceStatusEvent(deviceId,DeviceStatusEvent.Status.ONLINE));

    }

    /**
     * 用于不断的给服务端发送消息，证明自己还在线，
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("我还在线");
        //设备每次向我们发送数据,我们就可以认为设备是在线的,我们就更新一下在线时间
        //设备发送什么数据我们认为是在线的?其实发送什么都可以
        String deviceId = (String) session.getAttributes().get("deviceId");
        updateTimes.put(deviceId,System.currentTimeMillis());
//        可以通过温湿度数据判断是否在线
        String payload = message.getPayload();
        if("ping".equals(payload)){
            session.sendMessage(new TextMessage("ping"));
        }
        WebSocketSession webSocketSession = allClients.get(deviceId);
        if (webSocketSession != null) {
            allClients.put(deviceId,webSocketSession);
        }
    }

    /**
     * 关闭连接成功之后
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String deviceId = (String) session.getAttributes().get("deviceId");
        allClients.remove(deviceId);
        updateTimes.remove(deviceId);
        context.publishEvent(new DeviceStatusEvent(deviceId,DeviceStatusEvent.Status.OFFLINE));
    }
    protected void sendMessage(String deviceId,String command){
        WebSocketSession webSocketSession = allClients.get(deviceId);
        if (webSocketSession != null && webSocketSession.isOpen()) {
            try {
                webSocketSession.sendMessage(new TextMessage(command));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    //我们需要不断的重复的检查设备是不是在线,也就是检查每个设备的最后连接时间和当前的连接时间是不是超出六十秒
    //我们可以每隔一分钟检查一次,所以是一个定时任务
    @EventListener
    @Async
    public void onCheckStatus(CheckStatusTask checkStatus) {
        if (isNeedUpdateStatus()) {
            HashMap<String, Long> hashMap = new HashMap<>();
            hashMap.putAll(updateTimes);
            //map不能一边遍历一边删除
            hashMap.forEach((deviceId,time)->{
                //获取到系统的当前时间
                long currentTimeMillis = System.currentTimeMillis();
                if (currentTimeMillis - time > 60000) {
                    //超出了我们限制
//                    如果指定的键没有对应的值(没有该键或者该键对应的值是空), 那么使用该键计算新的值,并将其添加到 Map 中
//                allClients.compute(deviceId, (key, session) -> {
//                    try {
//
//                        //关闭会话
//                        session.close();
//                    } catch (IOException e) {
//                        throw new RuntimeException(e);
//                    }
//                    return null;
//                });
                    WebSocketSession session = allClients.get(deviceId);
                    if (session != null) {
                        try {
                            session.close();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }

                }
            });
        }
    }

    public abstract boolean isNeedUpdateStatus();
}
